#include <linux/err.h>
#include <linux/fs2.h>
#include <linux/sched.h>
#include <linux/cache.h>
#include <linux/gfp.h>
#include <linux/log2.h>

#define HASH_SHIFT ilog2(PAGE_SIZE / sizeof(struct list_head))
#define HASH_SIZE (1UL << HASH_SHIFT)

static struct list_head *mount_hashtable;

static inline unsigned long hash(struct vfsmount *mnt, struct dentry *dentry)
{
	unsigned long tmp = ((unsigned long)mnt / L1_CACHE_BYTES);
	tmp += ((unsigned long)dentry / L1_CACHE_BYTES);
	tmp = tmp + (tmp >> HASH_SHIFT);
	return tmp & (HASH_SIZE - 1);
}

void simple_set_mnt(struct vfsmount *mnt, struct super_block *sb)
{
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	mnt->mnt_sb = sb;
	mnt->mnt_root = dget(sb->s_root);
}

struct vfsmount *alloc_vfsmnt(const char *name)
{
	// struct vfsmount *mnt = kmem_cache_zalloc(mnt_cache, GFP_KERNEL);
	struct vfsmount *mnt = (struct vfsmount *)kmalloc(sizeof(struct vfsmount));
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	if (mnt) {
    	printf("this is %s(): %d\r\n", __func__, __LINE__);
		int err;

		// err = mnt_alloc_id(mnt);
		// if (err)
		// 	goto out_free_cache;

		// if (name) {
		// 	mnt->mnt_devname = kstrdup(name, GFP_KERNEL);
		// 	if (!mnt->mnt_devname)
		// 		goto out_free_id;
		// }

		// atomic_set(&mnt->mnt_count, 1);
		INIT_LIST_HEAD(&mnt->mnt_hash);
		INIT_LIST_HEAD(&mnt->mnt_child);
		INIT_LIST_HEAD(&mnt->mnt_mounts);
		INIT_LIST_HEAD(&mnt->mnt_list);
		INIT_LIST_HEAD(&mnt->mnt_expire);
		INIT_LIST_HEAD(&mnt->mnt_share);
		INIT_LIST_HEAD(&mnt->mnt_slave_list);
		INIT_LIST_HEAD(&mnt->mnt_slave);
// #ifdef CONFIG_SMP
// 		mnt->mnt_writers = alloc_percpu(int);
// 		if (!mnt->mnt_writers)
// 			goto out_free_devname;
// #else
		mnt->mnt_writers = 0;
// #endif
    	printf("this is %s(): %d\r\n", __func__, __LINE__);
	}
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	return mnt;

// #ifdef CONFIG_SMP
// out_free_devname:
// 	kfree(mnt->mnt_devname);
// #endif
out_free_id:
	// mnt_free_id(mnt);
out_free_cache:
	// kmem_cache_free(mnt_cache, mnt);
	return NULL;
}

static void init_mount_tree(void)
{
	struct vfsmount *mnt;
	// struct mnt_namespace *ns;
	struct path root;

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	// mnt = do_kern_mount("rootfs", 0, "rootfs", NULL);
	mnt = do_kern_mount("romfs", 0, "romfs", NULL);
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	// if (IS_ERR(mnt))
		// panic("Can't create rootfs");
	// ns = create_mnt_ns(mnt);
	// if (IS_ERR(ns))
	// 	panic("Can't allocate initial namespace");

	// init_task.nsproxy->mnt_ns = ns;
	// get_mnt_ns(ns);

	// root.mnt = ns->root;
	// root.dentry = ns->root->mnt_root;
	root.mnt = mnt;
	root.dentry = mnt->mnt_root;

	set_fs_pwd(current->fs, &root);
	set_fs_root(current->fs, &root);
	printf("this is %s(): %d\r\n", __func__, __LINE__);
}

#if 1
struct vfsmount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry,
			      int dir)
{
	struct list_head *head = mount_hashtable + hash(mnt, dentry);
	struct list_head *tmp = head;
	struct vfsmount *p, *found = NULL;

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	for (;;) {
		printf("this is %s(): %d\r\n", __func__, __LINE__);
		tmp = dir ? tmp->next : tmp->prev;
		p = NULL;
		if (tmp == head)
			break;
		p = list_entry(tmp, struct vfsmount, mnt_hash);
		if (p->mnt_parent == mnt && p->mnt_mountpoint == dentry) {
			printf("this is %s(): %d\r\n", __func__, __LINE__);
			found = p;
			break;
			printf("this is %s(): %d\r\n", __func__, __LINE__);
		}
		printf("this is %s(): %d\r\n", __func__, __LINE__);
	}
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	printf("this is %s(): %d >>> found->mnt_sb->s_type->name = %s\r\n", __func__, __LINE__, found->mnt_sb->s_type->name);
	return found;
}

struct vfsmount *lookup_mnt(struct path *path)
{
	struct vfsmount *child_mnt;
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	// spin_lock(&vfsmount_lock);
	if ((child_mnt = __lookup_mnt(path->mnt, path->dentry, 1)))
		mntget(child_mnt);
	// spin_unlock(&vfsmount_lock);
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	printf("this is %s(): %d >>> child_mnt->mnt_sb->s_type->name = %s\r\n", __func__, __LINE__, child_mnt->mnt_sb->s_type->name);
	return child_mnt;
}

static void commit_tree(struct vfsmount *mnt)
{
	struct vfsmount *parent = mnt->mnt_parent;
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	// struct vfsmount *m;
	// LIST_HEAD(head);
	// struct mnt_namespace *n = parent->mnt_ns;

	// BUG_ON(parent == mnt);

	// list_add_tail(&head, &mnt->mnt_list);
	// list_for_each_entry(m, &head, mnt_list)
		// m->mnt_ns = n;
	// list_splice(&head, n->list.prev);

	list_add_tail(&mnt->mnt_hash, mount_hashtable +
				hash(parent, mnt->mnt_mountpoint));
	list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
	// touch_mnt_namespace(n);
}

void mnt_set_mountpoint(struct vfsmount *mnt, struct dentry *dentry,
			struct vfsmount *child_mnt)
{
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	printf("this is %s(): %d >>> mnt->mnt_sb->s_type->name = %s\r\n", __func__, __LINE__, mnt->mnt_sb->s_type->name);
	printf("this is %s(): %d >>> child_mnt->mnt_sb->s_type->name = %s\r\n", __func__, __LINE__, child_mnt->mnt_sb->s_type->name);
	child_mnt->mnt_parent = mntget(mnt);
	child_mnt->mnt_mountpoint = dget(dentry);
	dentry->d_mounted++;
}

static int attach_recursive_mnt(struct vfsmount *source_mnt,
			struct path *path, struct path *parent_path)
{
	LIST_HEAD(tree_list);
	struct vfsmount *dest_mnt = path->mnt;
	struct dentry *dest_dentry = path->dentry;
	struct vfsmount *child, *p;
	int err;

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	// if (IS_MNT_SHARED(dest_mnt)) {
	// 	err = invent_group_ids(source_mnt, true);
	// 	if (err)
	// 		goto out;
	// }
	// err = propagate_mnt(dest_mnt, dest_dentry, source_mnt, &tree_list);
	// if (err)
	// 	goto out_cleanup_ids;

	// if (IS_MNT_SHARED(dest_mnt)) {
	// 	for (p = source_mnt; p; p = next_mnt(p, source_mnt))
	// 		set_mnt_shared(p);
	// }

	// spin_lock(&vfsmount_lock);
	if (parent_path) {
		printf("this is %s(): %d\r\n", __func__, __LINE__);
		// detach_mnt(source_mnt, parent_path);
		// attach_mnt(source_mnt, path);
		// touch_mnt_namespace(parent_path->mnt->mnt_ns);
	} else {
		printf("this is %s(): %d\r\n", __func__, __LINE__);
		mnt_set_mountpoint(dest_mnt, dest_dentry, source_mnt);
		commit_tree(source_mnt);
	}

	list_for_each_entry_safe(child, p, &tree_list, mnt_hash) {
		printf("this is %s(): %d\r\n", __func__, __LINE__);
		list_del_init(&child->mnt_hash);
		commit_tree(child);
	}
	// spin_unlock(&vfsmount_lock);
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	return 0;

 out_cleanup_ids:
	// if (IS_MNT_SHARED(dest_mnt))
		// cleanup_group_ids(source_mnt, NULL);
 out:
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	return err;
}

static int graft_tree(struct vfsmount *mnt, struct path *path)
{
	int err;
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	// if (mnt->mnt_sb->s_flags & MS_NOUSER)
	// 	return -EINVAL;

	// if (S_ISDIR(path->dentry->d_inode->i_mode) !=
	//       S_ISDIR(mnt->mnt_root->d_inode->i_mode))
	// 	return -ENOTDIR;

	// err = -ENOENT;
	// mutex_lock(&path->dentry->d_inode->i_mutex);
	// if (IS_DEADDIR(path->dentry->d_inode))
	// 	goto out_unlock;

	// err = security_sb_check_sb(mnt, path);
	// if (err)
	// 	goto out_unlock;

	// err = -ENOENT;
	// if (!d_unlinked(path->dentry))
		err = attach_recursive_mnt(mnt, path, NULL);
out_unlock:
	// mutex_unlock(&path->dentry->d_inode->i_mutex);
	// if (!err)
		// security_sb_post_addmount(mnt, path);
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	return err;
}

int do_add_mount(struct vfsmount *newmnt, struct path *path,
		 int mnt_flags, struct list_head *fslist)
{
	int err;

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	// down_write(&namespace_sem);
	/* Something was mounted here while we slept */
	// while (d_mountpoint(path->dentry) &&
	//        follow_down(path))
	// 	;
	// err = -EINVAL;
	// if (!(mnt_flags & MNT_SHRINKABLE) && !check_mnt(path->mnt))
	// 	goto unlock;

	/* Refuse the same filesystem on the same mount point */
	// err = -EBUSY;
	// if (path->mnt->mnt_sb == newmnt->mnt_sb &&
	//     path->mnt->mnt_root == path->dentry)
	// 	goto unlock;

	// err = -EINVAL;
	// if (S_ISLNK(newmnt->mnt_root->d_inode->i_mode))
	// 	goto unlock;

	newmnt->mnt_flags = mnt_flags;
	if ((err = graft_tree(newmnt, path)))
		goto unlock;

	// if (fslist) /* add to the specified expiration list */
	// 	list_add_tail(&newmnt->mnt_expire, fslist);

	// up_write(&namespace_sem);
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	return 0;

unlock:
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	// up_write(&namespace_sem);
	// mntput(newmnt);
	return err;
}

static int do_new_mount(struct path *path, char *type, int flags,
			int mnt_flags, char *name, void *data)
{
	struct vfsmount *mnt;

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	if (!type)
		return -EINVAL;

	/* we need capabilities... */
	// if (!capable(CAP_SYS_ADMIN))
		// return -EPERM;

	// lock_kernel();
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	mnt = do_kern_mount(type, flags, name, data);
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	// unlock_kernel();
	if (IS_ERR(mnt))
		return PTR_ERR(mnt);

	return do_add_mount(mnt, path, mnt_flags, NULL);
}

int copy_mount_string(const void *data, char **where)
{
	char *tmp;

	if (!data) {
		*where = NULL;
		return 0;
	}

	tmp = strndup_user(data, PAGE_SIZE);
	if (IS_ERR(tmp))
		return PTR_ERR(tmp);

	*where = tmp;
	return 0;
}

long do_mount(char *dev_name, char *dir_name, char *type_page,
		  unsigned long flags, void *data_page)
{
	struct path path;
	int retval = 0;
	int mnt_flags = 0;

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	/* Discard magic */
	// if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
	// 	flags &= ~MS_MGC_MSK;

	/* Basic sanity checks */

	// if (!dir_name || !*dir_name || !memchr(dir_name, 0, PAGE_SIZE))
		// return -EINVAL;

	// if (data_page)
		// ((char *)data_page)[PAGE_SIZE - 1] = 0;

	/* Default to relatime unless overriden */
	// if (!(flags & MS_NOATIME))
	// 	mnt_flags |= MNT_RELATIME;

	/* Separate the per-mountpoint flags */
	// if (flags & MS_NOSUID)
	// 	mnt_flags |= MNT_NOSUID;
	// if (flags & MS_NODEV)
	// 	mnt_flags |= MNT_NODEV;
	// if (flags & MS_NOEXEC)
	// 	mnt_flags |= MNT_NOEXEC;
	// if (flags & MS_NOATIME)
	// 	mnt_flags |= MNT_NOATIME;
	// if (flags & MS_NODIRATIME)
	// 	mnt_flags |= MNT_NODIRATIME;
	// if (flags & MS_STRICTATIME)
	// 	mnt_flags &= ~(MNT_RELATIME | MNT_NOATIME);
	// if (flags & MS_RDONLY)
	// 	mnt_flags |= MNT_READONLY;

	// flags &= ~(MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE |
	// 	   MS_NOATIME | MS_NODIRATIME | MS_RELATIME| MS_KERNMOUNT |
	// 	   MS_STRICTATIME);

	/* ... and get the mountpoint */
	retval = kern_path(dir_name, LOOKUP_FOLLOW, &path);
	if (retval)
		return retval;

	printf("this is %s(): %d >>> path.dentry->d_name.name = %s\r\n", __func__, __LINE__, path.dentry->d_name.name);

	// retval = security_sb_mount(dev_name, &path,
	// 			   type_page, flags, data_page);
	// if (retval)
	// 	goto dput_out;

	// if (flags & MS_REMOUNT)
	// 	retval = do_remount(&path, flags & ~MS_REMOUNT, mnt_flags,
	// 			    data_page);
	// else if (flags & MS_BIND)
	// 	retval = do_loopback(&path, dev_name, flags & MS_REC);
	// else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
	// 	retval = do_change_type(&path, flags);
	// else if (flags & MS_MOVE)
	// 	retval = do_move_mount(&path, dev_name);
	// else
		retval = do_new_mount(&path, type_page, flags, mnt_flags,
				      dev_name, data_page);
dput_out:
	// path_put(&path);
	return retval;
}

int sys_mount(char *dev_name, char *dir_name, char *type, unsigned long flags, void *data)
{
	int ret;
	char *kernel_type;
	char *kernel_dir;
	char *kernel_dev;
	unsigned long data_page;

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	printf("this is %s(): %d >>> dev_name = %s\r\n", __func__, __LINE__, dev_name);
	printf("this is %s(): %d >>> dir_name = %s\r\n", __func__, __LINE__, dir_name);
	printf("this is %s(): %d >>> type = %s\r\n", __func__, __LINE__, type);

	ret = copy_mount_string(type, &kernel_type);
	if (ret < 0)
		goto out_type;

	kernel_dir = getname(dir_name);
	if (IS_ERR(kernel_dir)) {
		ret = PTR_ERR(kernel_dir);
		goto out_dir;
	}

	ret = copy_mount_string(dev_name, &kernel_dev);
	if (ret < 0)
		goto out_dev;

	// ret = copy_mount_options(data, &data_page);
	// if (ret < 0)
	// 	goto out_data;

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	ret = do_mount(kernel_dev, kernel_dir, kernel_type, flags,
		(void *) data_page);

	// free_page(data_page);
out_data:
	kfree(kernel_dev);
out_dev:
	// putname(kernel_dir);
out_dir:
	kfree(kernel_type);
out_type:
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	return ret;
}
#endif

void mnt_init(void)
{
	unsigned u;
	// int err;

	printf("this is %s(): %d\r\n", __func__, __LINE__);

	// init_rwsem(&namespace_sem);

	// mnt_cache = kmem_cache_create("mnt_cache", sizeof(struct vfsmount),
	// 		0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);

	// mount_hashtable = (struct list_head *)__get_free_page(GFP_ATOMIC);
	mount_hashtable = (struct list_head *)__get_free_pages(GFP_ATOMIC, 1);

	if (!mount_hashtable) {
		// panic("Failed to allocate mount hash table\n");
		printf("Failed to allocate mount hash table\n");
		while (1);
	}

	printf("Mount-cache hash table entries: %lu\n", HASH_SIZE);

	for (u = 0; u < HASH_SIZE; u++)
		INIT_LIST_HEAD(&mount_hashtable[u]);

	// err = sysfs_init();
	// if (err)
	// 	printk(KERN_WARNING "%s: sysfs_init error: %d\n",
	// 		__func__, err);
	// fs_kobj = kobject_create_and_add("fs", NULL);
	// if (!fs_kobj)
	// 	printk(KERN_WARNING "%s: kobj create error\n", __func__);
	init_rootfs();
	init_mount_tree();
}
